package deadlock

import (
	"fmt"
	"github.com/petermattis/goid"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

// An RWMutex is a drop-in replacement for sync.RWMutex.
// Performs deadlock detection unless disabled in Options.
type RWMutex struct {
	mu sync.RWMutex
}

// Lock locks rw for writing.
// If the lock is already locked for reading or writing,
// Lock blocks until the lock is available.
// To ensure that the lock eventually becomes available,
// a blocked Lock call excludes new readers from acquiring
// the lock.
//
// Unless deadlock detection is disabled, logs potential deadlocks to Options.LogBuf,
// calling Options.OnPotentialDeadlock on each occasion.
func (m *RWMutex) Lock() {
	lock(m.mu.Lock, m)
}

// Unlock unlocks the mutex for writing.  It is a run-time error if rw is
// not locked for writing on entry to Unlock.
//
// As with Mutexes, a locked RWMutex is not associated with a particular
// goroutine.  One goroutine may RLock (Lock) an RWMutex and then
// arrange for another goroutine to RUnlock (Unlock) it.
func (m *RWMutex) Unlock() {
	m.mu.Unlock()
	if Options.Disable {
		return
	}
	lh.afterUnlock(m)
}

// RLock locks the mutex for reading.
//
// Unless deadlock detection is disabled, logs potential deadlocks to Options.LogBuf,
// calling Options.OnPotentialDeadlock on each occasion.
func (m *RWMutex) RLock() {
	lock(m.mu.RLock, m)
}

// RUnlock undoes a single RLock call;
// it does not affect other simultaneous readers.
// It is a run-time error if rw is not locked for reading
// on entry to RUnlock.
func (m *RWMutex) RUnlock() {
	m.mu.RUnlock()
	if Options.Disable {
		return
	}
	lh.afterUnlock(m)
}

// An RWMutex is a drop-in replacement for sync.RWMutex.
// Performs deadlock detection unless disabled in Options.
type Mutex struct {
	mu sync.Mutex
}

// Lock locks rw for writing.
// If the lock is already locked for reading or writing,
// Lock blocks until the lock is available.
// To ensure that the lock eventually becomes available,
// a blocked Lock call excludes new readers from acquiring
// the lock.
//
// Unless deadlock detection is disabled, logs potential deadlocks to Options.LogBuf,
// calling Options.OnPotentialDeadlock on each occasion.
func (m *Mutex) Lock() {
	lock(m.mu.Lock, m)
}

// Unlock unlocks the mutex for writing.  It is a run-time error if rw is
// not locked for writing on entry to Unlock.
//
// As with Mutexes, a locked RWMutex is not associated with a particular
// goroutine.  One goroutine may RLock (Lock) an RWMutex and then
// arrange for another goroutine to RUnlock (Unlock) it.
func (m *Mutex) Unlock() {
	m.mu.Unlock()
	if Options.Disable {
		return
	}
	lh.afterUnlock(m)
}

func lock(lockFn func(), ptr interface{}) {
	if Options.Disable {
		lockFn()
		return
	}
	ch := make(chan struct{})
	gid := goid.Get()
	stack := callers(2)
	rec := StackRecord{Gid: gid, Stack: stack, T0: time.Now().UnixMilli()}
	go checkDeadlock(ptr, &rec, ch)
	lockFn()
	lh.afterLock(ptr, rec)
	close(ch)
}

var timersPool sync.Pool

func checkDeadlock(ptr interface{}, stack *StackRecord, ch <-chan struct{}) {
	t := acquireTimer(Options.DeadlockTimeout)
	defer releaseTimer(t)
	for {
		select {
		case <-t.C:
			Options.OnPotentialDeadlock(ptr, stack)
			return
		case <-ch:
			return
		}
		t.Reset(Options.DeadlockTimeout)
	}
}

func acquireTimer(d time.Duration) *time.Timer {
	t, ok := timersPool.Get().(*time.Timer)
	if ok {
		_ = t.Reset(d)
		return t
	}
	return time.NewTimer(d)
}

func StrToFloat64Def(s string, defval float64) float64 {
	v, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return defval
	} else {
		return float64(v)
	}
}

// 示例版本检测（实际项目中应该用 build tags 更可靠）
func isGo123OrHigher() bool {
	version := runtime.Version()
	strs := strings.SplitN(strings.TrimPrefix(version, "go"), ".", 3)
	version = fmt.Sprintf("%s.%s", strs[0], strs[1])
	return StrToFloat64Def(version, 0) >= 1.23
}

func releaseTimer(t *time.Timer) {
	if t == nil {
		return
	}
	t.Stop()
	timersPool.Put(t)
}

const header = "POTENTIAL DEADLOCK:"

var (
	versionGreater123 = isGo123OrHigher()
)
